// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/url_request/url_request_backoff_manager.h"

#include <memory>

#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {

namespace {

    class URLRequestBackoffManagerTest : public testing::Test {
    protected:
        URLRequestBackoffManagerTest()
            : manager_(new URLRequestBackoffManager)
        {
        }
        void RegisterURL(const GURL& url,
            int backoff_in_sec,
            const base::Time& request_time)
        {
            std::string raw_headers = base::StringPrintf(
                "HTTP/1.0 200 OK\n"
                "backoff: %d\n\n",
                backoff_in_sec);
            scoped_refptr<HttpResponseHeaders> headers(new HttpResponseHeaders(
                HttpUtil::AssembleRawHeaders(raw_headers.c_str(), raw_headers.size())));
            manager_->UpdateWithResponse(url, headers.get(), request_time);
        }

        std::unique_ptr<URLRequestBackoffManager> manager_;
    };
} // namespace

TEST_F(URLRequestBackoffManagerTest, ShouldRejectRequest)
{
    base::Time request_time = base::Time::Now();
    RegisterURL(GURL("https://example.com"), 3600, request_time);
    ASSERT_EQ(1, manager_->GetNumberOfEntriesForTests());
    ASSERT_TRUE(manager_->ShouldRejectRequest(
        GURL("https://example.com?q=v"),
        request_time + base::TimeDelta::FromSeconds(3500)));

    // Only can try once in the interval of
    // [|request_time| + 3600, |request_time| + 3600 * 1.1).
    ASSERT_FALSE(manager_->ShouldRejectRequest(
        GURL("https://example.com?q=v"),
        request_time + base::TimeDelta::FromSeconds(3700)));
    ASSERT_TRUE(manager_->ShouldRejectRequest(
        GURL("https://example.com?q=v"),
        request_time + base::TimeDelta::FromSeconds(3700)));
    ASSERT_EQ(1, manager_->GetNumberOfEntriesForTests());

    // After release time, throttling should not be applied.
    ASSERT_FALSE(manager_->ShouldRejectRequest(
        GURL("https://example.com?q=v"),
        request_time + base::TimeDelta::FromSeconds(3960)));
}

TEST_F(URLRequestBackoffManagerTest, MisconfiguredHeaders)
{
    // Backoff time is smaller than the minimum allowed.
    RegisterURL(GURL("https://example.com"),
        URLRequestBackoffManager::kMinimumBackoffInSeconds - 1,
        base::Time::Now());
    ASSERT_EQ(0, manager_->GetNumberOfEntriesForTests());

    // Backoff time is bigger than the maximum allowed.
    RegisterURL(GURL("https://example.com"),
        URLRequestBackoffManager::kMaximumBackoffInSeconds + 1,
        base::Time::Now());
    ASSERT_EQ(0, manager_->GetNumberOfEntriesForTests());
}

TEST_F(URLRequestBackoffManagerTest, ShouldGarbageCollect)
{
    base::Time request_time = base::Time::Now() - base::TimeDelta::FromSeconds(60);
    for (int i = 0;
         i < URLRequestBackoffManager::kNewEntriesBetweenCollecting - 1; i++) {
        RegisterURL(GURL(base::StringPrintf("http://example%d.com", i)), 10,
            request_time);
        ASSERT_EQ(i + 1, manager_->GetNumberOfEntriesForTests());
    }
    // Should clear all previous outdated entries.
    RegisterURL(GURL("http://example.com"), 10, base::Time::Now());
    ASSERT_EQ(1, manager_->GetNumberOfEntriesForTests());
}

TEST_F(URLRequestBackoffManagerTest, ClearOnNetworkChange)
{
    for (int i = 0; i < 3; ++i) {
        RegisterURL(GURL("http://www.example.com/"), 60, base::Time::Now());
        ASSERT_EQ(1, manager_->GetNumberOfEntriesForTests());
        EXPECT_TRUE(manager_->ShouldRejectRequest(GURL("http://www.example.com/"),
            base::Time::Now()));
        switch (i) {
        case 0:
            manager_->OnIPAddressChanged();
            break;
        case 1:
            manager_->OnConnectionTypeChanged(
                NetworkChangeNotifier::CONNECTION_UNKNOWN);
            break;
        case 2:
            manager_->OnConnectionTypeChanged(
                NetworkChangeNotifier::CONNECTION_NONE);
            break;
        default:
            FAIL();
        }

        EXPECT_FALSE(manager_->ShouldRejectRequest(GURL("http://www.example.com/"),
            base::Time::Now()));
        ASSERT_EQ(0, manager_->GetNumberOfEntriesForTests());
    }
}

} // namespace net
